﻿#region Copyright

// The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in compliance
//  with the License. You may obtain a copy of the License at
//  
//  http://www.mozilla.org/MPL/
//  
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations under 
//  the License.
//  
//  The Initial Developer of the Original Code is Robert Smyth.
//  Portions created by Robert Smyth are Copyright (C) 2008.
//  
//  All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using NUnitGrid.Framework.Logging;


namespace NUnitGrid.Framework
{
    public class Schedule : IScheduler, ISchedule
    {
        private readonly IClock clock;
        private readonly List<ScheduledEvent> events = new List<ScheduledEvent>();
        private readonly ILogger logger;
        private TimeSpan tickPeriod = TimeSpan.FromMilliseconds(25);
        private Timer tickTimer;

        public Schedule(IClock clock, ILogger logger)
        {
            this.clock = clock;
            this.logger = logger;
        }

        public void AddEvent(IScheduledEventListener listener, TimeSpan timeToEvent)
        {
            events.Add(new ScheduledEvent(clock, timeToEvent, listener, logger));
        }

        public void RemoveEventsFor(IScheduledEventListener listener)
        {
            var eventsToRemove = new List<ScheduledEvent>();

            foreach (var scheduledEvent in events)
            {
                if (scheduledEvent.IsEventFor(listener))
                {
                    eventsToRemove.Add(scheduledEvent);
                }
            }

            foreach (var scheduledEvent in eventsToRemove)
            {
                events.Remove(scheduledEvent);
            }
        }

        public void Start()
        {
            tickTimer = new Timer {Interval = ((int) tickPeriod.TotalMilliseconds)};
            tickTimer.Tick += tickTimer_Tick;
            tickTimer.Start();
        }

        public void Stop()
        {
            tickTimer.Stop();
            events.Clear();
        }

        private void tickTimer_Tick(object sender, EventArgs e)
        {
            var expiredEvents = new List<ScheduledEvent>();

            foreach (var scheduledEvent in events)
            {
                if (scheduledEvent.TimeExpired)
                {
                    expiredEvents.Add(scheduledEvent);
                }
            }

            foreach (var expiredEvent in expiredEvents)
            {
                events.Remove(expiredEvent);
                expiredEvent.Fire();
            }
        }

        private class ScheduledEvent : IEqualityComparer<ScheduledEvent>
        {
            private readonly IClock clock;
            private readonly DateTime expireTime;
            private readonly IScheduledEventListener listener;

            public ScheduledEvent(IClock clock, TimeSpan timeToEvent, IScheduledEventListener listener, ILogger logger)
            {
                this.clock = clock;
                this.listener = listener;
                expireTime = clock.Now + timeToEvent;
            }

            public bool TimeExpired
            {
                get { return clock.Now >= expireTime; }
            }

            public bool Equals(ScheduledEvent x, ScheduledEvent y)
            {
                return x.GetHashCode() == y.GetHashCode();
            }

            public int GetHashCode(ScheduledEvent obj)
            {
                return base.GetHashCode();
            }

            public void Fire()
            {
                listener.OnEventFired();
            }

            public bool IsEventFor(IScheduledEventListener someListener)
            {
                return listener == someListener;
            }
        }
    }
}